/**
 * @file web存储
 * @author its-wild(https://gitee.com/its-wild)
 * @date 2022/07/05
 */
import { WebStorageEvent } from './webStorageEvent'
import { TOptions } from '../index'
import { encryptStr, decryptStr } from '../utils/index.js'


interface Win extends Window {
  attachEvent?: (eventkey: string, callback: () => void) => void
  [props: string]: any
}

type TWin = Win & typeof globalThis

const win = window as TWin


export class WebStorage {
  public storage: Storage
  public options: TOptions
  public length: number

  constructor(options: TOptions, storage: Storage) {
    this.storage = storage
    this.options = options
    this.length = 0

    // 劫持 length 属性
    Object.defineProperty(this, 'length', {
      /**
       * @return {number}
       */
      get() {
        return this.storage.length
      },
    })

    if (typeof win !== 'undefined') {
      // 绑定监听事件
      this.options.events?.forEach((val, i) => {
        const eventkey = val as keyof WindowEventMap
        if (win.addEventListener) {
          // 注意：该事件不在导致数据变化的当前页面触发（如果浏览器同时打开一个域名下面的多个页面，当其中的一个页面改变 sessionStorage 或 localStorage 的数据时，其他所有页面的  storage  事件会被触发，而原始页面并不触发 storage 事件）
          win.addEventListener(eventkey, (e) => {
            WebStorageEvent.emit(e as StorageEvent)
          }, false)
        } else if (win.attachEvent) {
          win.attachEvent(`on${eventkey}`, WebStorageEvent.emit)
        } else {
          win[`on${eventkey}`] = WebStorageEvent.emit
        }
      })
    }
  }

  /**
   * 移除web存储
   * @param {string} name 存储的键
   * @return {boolean} 是否移除成功
   */
  public remove(name: string) {
    return this.storage.removeItem(this.options.namespace + name)
  }

  /**
   * 设置web存储
   * @param {string} name 存储的键
   * @param {*} value 存储的值
   * @param {number | null} expire 毫秒
   * @param {boolean} encryption 是否要对单条加密
   * @param {string} publicKey 公钥
   * @return {boolean} 设置成功
   */
  public set(name: string, value: any, expire?: number | null, encryption?: boolean, publicKey?: string): boolean {
    let stringifyValue = JSON.stringify({
      value,
      expire: expire ? new Date().getTime() + expire : null,
    })
    // 1. 单个不加密情况
    if (encryption === false) {
      this.storage.setItem(this.options.namespace + name, stringifyValue)
      return true
    }

    // 2. 单个要加密情况
    if (encryption === true) {
      const _publicKey = publicKey || this.options.publicKey!
      const encryptValue = encryptStr(_publicKey, stringifyValue)
      if (encryptValue === false) {
        throw new Error(`its-storage: Encryption failed`)
      } else {
        stringifyValue = encryptValue
        this.storage.setItem(this.options.namespace + name, stringifyValue)
        return true
      }
    }

    // 3. 没有单个的加密控制，就要处理全局的加密控制
    if (this.options.encryption === true) {
      const encryptValue = encryptStr(this.options.publicKey!, stringifyValue)
      if (encryptValue === false) {
        throw new Error(`its-storage: Encryption failed`)
      } else {
        stringifyValue = encryptValue
        this.storage.setItem(this.options.namespace + name, stringifyValue)
        return true
      }
    } else {
      this.storage.setItem(this.options.namespace + name, stringifyValue)
      return true
    }
  }

  /**
   * 获取web存储
   * @param {string} name 存储的键
   * @param {*} def 没有获取到时的默认值
   * @param {boolean} decryption 是否要对单条解密
   * @param {string} privateKey 私钥
   * @returns {*} 存储的值
   */
  public get(name: string, def: any = null, decryption?: boolean, privateKey?: string) {
    const item = this.storage.getItem(this.options.namespace + name)

    if (item !== null) {
      try {
        let data
        // 1. 单条不用解密
        if (decryption === false) {
          data = JSON.parse(item)
        } else if (decryption === true || this.options.encryption === true) {
          // 2. 单条要解密 或 全局加密了
          const _privateKey = privateKey || this.options.privateKey!
          const decryptValue = decryptStr(_privateKey, item)
          if (decryptValue === false) {
            throw new Error(`its-storage: Decryption failed`)
          } else {
            data = JSON.parse(decryptValue)
          }
        } else {
          data = JSON.parse(item)
        }

        if (data.expire === null) {
          return data.value
        }

        if (data.expire >= new Date().getTime()) {
          return data.value
        }

        this.remove(name)

      } catch (err) {
        return def
      }
    }

    return def
  }

  /**
   * 清除web存储
   */
  clear() {
    if (this.length === 0) return

    const removedKeys = []

    for (let i = 0; i < this.length; i++) {
      const key = this.storage.key(i)
      const regexp = new RegExp(`^${this.options.namespace}.+`, 'i')

      if (regexp.test(key!) === false) {
        continue
      }

      removedKeys.push(key!)
    }

    for (const key in removedKeys) {
      this.storage.removeItem(removedKeys[key])
    }
  }

  /**
   * 监听web存储值的变化
   * @param {string} name
   * @param {Function} callback
   */
  on(name: string, callback: (newValue: any, oldValue: any, url: string) => void) {
    WebStorageEvent.on(this.options.namespace + name, callback)
  }

  /**
   * 移除监听web存储值的变化
   * @param {string} name
   * @param {Function} callback
   */
  off(name: string, callback: (newValue: any, oldValue: any, url: string) => void) {
    WebStorageEvent.off(this.options.namespace + name, callback)
  }
}
